package com.ruoyi.system.service.impl;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.exception.CustomException;
import com.ruoyi.common.utils.BarcodeUtils;
import com.ruoyi.common.utils.MessageUtil;
import com.ruoyi.system.domain.OfficialAccount;
import com.ruoyi.system.domain.OfficialDetail;
import com.ruoyi.system.domain.vo.WxCallBackParamVO;
import com.ruoyi.system.domain.vo.WxPushTemplateVO;
import com.ruoyi.system.service.*;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.batik.util.EventDispatcher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;

import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.TimeUnit;

@SuppressWarnings("AlibabaLowerCamelCaseVariableNaming")
@Slf4j
@Service
public class WXMessageServiceImpl implements IWXMessageService {

    private static final String CALLBACK_TOKEN = "0pcxuCAl9oGiFxx5oIE08MH7c";

    @Value("${notification.wechat.appId:}")
    private String appId;
    @Value("${notification.wechat.secret:}")
    private String secret;
    @Value("${notification.wechat.template.alarm:}")
    private String alarmTemplate;


    /**
     * 请求服务地址
     */
    private static final String BASE_URL = "https://api.weixin.qq.com/cgi-bin";

    @Autowired
    private RedisCache redisCache;
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private IOfficialAccountService officialAccountService;
    @Autowired
    private ISysUserService sysUserService;
    @Autowired
    private IOfficialDetailService officialDetailService;
    @Autowired
    private IAsyncDetailService asyncDetailService;

    @Override
    public String getToken(Boolean enforce) {
        //OfficialAccount account = getAccount();
        if (!enforce) {
            //Object tokenCache = redisCache.getCacheObject("WX_TOKEN_CACHE_" + account.getCompanyId());
            Object tokenCache = redisCache.getCacheObject("WX_TOKEN_CACHE_");
            if (tokenCache != null) {
                return (String) tokenCache;
            }
        }
        UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(BASE_URL + "/token");
        builder.queryParam("grant_type", "client_credential");
        builder.queryParam("appid", appId);
        builder.queryParam("secret", secret);
        UriComponents uriComponents = builder.build();
        ResponseEntity<JSONObject> resp = null;
        try {
            resp = restTemplate.getForEntity(uriComponents.encode().toUri(), JSONObject.class);
        } catch (Exception e) {
            log.error("获取微信公众号Token异常，异常原因{}", e.getMessage());
        }
        if (resp == null || resp.getBody() == null) {
            throw new CustomException("获取微信公众号Token异常");
        }
        JSONObject jsonObject = resp.getBody();
        if (!StringUtils.isEmpty(jsonObject.getString("errcode"))) {
            throw new CustomException("获取微信公众号Token异常,异常原因" + jsonObject.getString("errmsg"));
        }
        String accessToken = jsonObject.getString("access_token");
        Integer expiresIn = jsonObject.getInteger("expires_in");
        //设置到redis中缓存
        redisCache.setCacheObject("WX_TOKEN_CACHE_", accessToken, expiresIn - 600, TimeUnit.SECONDS);
        return accessToken;
    }

    private JSONObject build(String template, JSONObject parameters) {
        JSONObject data = null;
        try {
            String tpl = FileService.load(String.format("/notification/wechat/template/%s.tpl", template), false);
            Set<String> keys = parameters.keySet();
            for (String key : keys) {
                String value = parameters.getString(key);
                if (value == null) {
                    continue;
                }
                tpl = tpl.replaceAll("\\$\\{" + key + "\\}", value);
            }
            data = JSON.parseObject(tpl);
            log.info("{}", data);
        } catch (Exception e) {
            log.info("",e);
            StackTraceElement[] traces = e.getStackTrace();
            for (StackTraceElement trace : traces) {
                log.debug(trace.toString());
            }
        }
        return data;
    }

    private JSONObject pushTemplateMessageToOne(JSONObject message) {
        JSONObject result = new JSONObject();
        try {
            String token = getToken(false);
            String url = BASE_URL + "/message/template/send?access_token=" + token;
            ResponseEntity<JSONObject> resp = restTemplate.postForEntity(url, message, JSONObject.class);
            log.info("wechat template message: {} => {}", message, resp);
            String code = null;
            String msg = null;
            if (resp == null || resp.getBody() == null) {
                code = "-1";
                msg = "微信推送接口响应内容为空";
            } else {
                code = resp.getBody().getString("errcode");
                msg = resp.getBody().getString("errmsg");
            }
            result.put("code", code);
            result.put("msg", msg);
        } catch (Exception e) {
            log.info("",e);
            StackTraceElement[] traces = e.getStackTrace();
            for (StackTraceElement trace : traces) {
                log.debug(trace.toString());
            }
        }
        return result;
    }

    @Override
    @Async
    public Boolean pushTemplateMessage(JSONObject request, String tid) {
        JSONArray tos = null;
        Long companyId = null;
        String type = null;
        String template = null;
        JSONObject parameters = null;
        boolean flag = false;
        try {
            companyId = request.getLong("companyId");
            parameters = request.getJSONObject("parameters");
            JSONObject message = new JSONObject();
            message.put("appid", appId);
            type = request.getString("type");
            if ("alarm".equals(type)) {
                template = alarmTemplate;
            }
            message.put("template_id", template);
            message.put("url", null);
            message.put("miniprogram", null);
            message.put("pagepath", null);
            JSONObject data = build(type, parameters);
            message.put("data", data);
            //9TmSDUS6S8O1K1Af1BLgexfUBVXdiWcZ3SS3ecp551E
            tos = request.getJSONArray("receivers");
            JSONObject result = null;
            for (int i = 0; i < tos.size(); i++) {
                String to = tos.getString(i);
                message.put("touser", to);
                result = pushTemplateMessageToOne(message);
                OfficialDetail detail = new OfficialDetail();
                detail.setAppId(appId);
                detail.setCode(template);
                detail.setCompanyId(companyId);
                detail.setErrmsg(result.getString("msg"));
                detail.setFlag(StrUtil.equals("0", result.getString("code")) ? "1" : "0");
                detail.setOpenId(to);
                detail.setParameters(JSONUtil.toJsonStr(parameters));
                detail.setType(type);
                detail.setTid(tid);
                detail.setCreateTime(new Date());
                officialDetailService.add(detail);
            }    
        } catch (Exception e) {
            log.info("",e);
            StackTraceElement[] traces = e.getStackTrace();
            for (StackTraceElement trace : traces) {
                log.debug(trace.toString());
            }
            flag = true;
            asyncDetailService.update(tid,2,e.getMessage());
        }
        if (!flag){
            asyncDetailService.update(tid,1,null);
        }
        return true;
    }

    @Override
    public void syncUserInfo() {
        //获取token
        String token = getToken(false);
        //构建参数
        UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(BASE_URL + "/user/get");
        builder.queryParam("access_token", token);
        //builder.queryParam("next_openid", "");
        UriComponents uriComponents = builder.build();
        ResponseEntity<JSONObject> resp = null;
        String errorMsg = null;
        try {
            resp = restTemplate.getForEntity(uriComponents.encode().toUri(), JSONObject.class);
        } catch (Exception e) {
            errorMsg = e.getMessage();
            log.error("获取微信公众号关注用户openId异常，异常原因{}", e.getMessage());
        }
        if (resp == null || resp.getBody() == null) {
            throw new CustomException("获取微信公众号关注用户openId异常,异常原因：" + errorMsg);
        }
        JSONObject body = resp.getBody();
        if (body.getJSONObject("data") != null && body.getJSONObject("data").getJSONArray("openid") != null) {
            JSONArray jsonArray = body.getJSONObject("data").getJSONArray("openid");
            for (int i = 0; i < jsonArray.size(); i++) {
                String openId = jsonArray.getString(i);
                // TODO 查询用户信息并更新
                getUserInfo(openId);
            }
        }
    }

    @Override
    public void getUserInfo(String openId) {
        //获取token
        String token = getToken(false);
        //构建参数
        UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(BASE_URL + "/user/info");
        builder.queryParam("access_token", token);
        builder.queryParam("openid", openId);
        builder.queryParam("lang", "zh_CN");
        UriComponents uriComponents = builder.build();
        ResponseEntity<JSONObject> resp = null;
        String errorMsg = null;
        try {
            resp = restTemplate.getForEntity(uriComponents.encode().toUri(), JSONObject.class);
        } catch (Exception e) {
            errorMsg = e.getMessage();
            log.error("获取微信公众号关注用户基本异常，异常原因{}", e.getMessage());
        }
        if (resp == null || resp.getBody() == null) {
            throw new CustomException("获取微信公众号关注用户基本异常,异常原因：" + errorMsg);
        }
        JSONObject body = resp.getBody();
        String subscribe = body.getString("subscribe");
        String openid = body.getString("openid");
        String nickname = body.getString("nickname");
        Integer sex = body.getInteger("sex");
        sex = sex == 1 ? 0 : sex == 2 ? 1 : 2;
        String city = body.getString("city");
        String province = body.getString("province");
        String country = body.getString("country");
        String headimgurl = body.getString("headimgurl");
        //单位秒
        Long subscribeTime = body.getLong("subscribe_time");
        Date date = new Date(subscribeTime * 1000);
        System.out.println(date);

        // TODO 根据openid在数据库中查询用户信息，如果没有查询到则新增，查询到了也修改


    }

    @Override
    public String createWeChatQRCode(SysUser sysUser) {
        if (sysUser == null) {
            throw new CustomException("没有查询到用户信息");
        }
        //获取token
        String token = getToken(false);
        String userName = sysUser.getUserName();
        Map<String, Object> param = new HashMap<>();
        //有效期设置成半小时
        param.put("expire_seconds", 1800);
        param.put("action_name", "QR_STR_SCENE");
        Map<String, Object> map = new HashMap<>();
        map.put("scene_str", "|" + userName);
        Map<String, Object> sceneMap = new HashMap<>();
        sceneMap.put("scene", map);
        param.put("action_info", sceneMap);
        String url = BASE_URL + "/qrcode/create?access_token=" + token;
        ResponseEntity<JSONObject> resp = null;
        String errorMsg = null;
        try {
            resp = restTemplate.postForEntity(url, JSON.toJSONString(param), JSONObject.class);
        } catch (Exception e) {
            errorMsg = e.getMessage();
            log.error("生成二维码异常，异常原因{}", e.getMessage());
        }
        if (resp == null || resp.getBody() == null) {
            throw new CustomException("生成二维码异常，异常原因:" + errorMsg);
        }
        JSONObject body = resp.getBody();
//        Map<String,Object> result = new HashMap<>();
//        result.put("ticket",body.getString("ticket"));
//        result.put("expireSeconds",body.getString("expire_seconds"));
//        result.put("url",body.getString("url"));
        return BarcodeUtils.generateQrcode(body.getString("url"), true);
    }

    @Override
    public Boolean checkSign(String signature, String timestamp, String nonce) {
        if (StringUtils.isEmpty(signature) || StringUtils.isEmpty(timestamp) || StringUtils.isEmpty(nonce)) {
            return false;
        }
        List<String> paramList = new ArrayList<>();
        paramList.add(CALLBACK_TOKEN);
        paramList.add(timestamp);
        paramList.add(nonce);
        Collections.sort(paramList);
        log.info("排序后的参数{}", paramList);
        StringBuffer sb = new StringBuffer();
        paramList.forEach(sb::append);
        String checktext = null;
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-1");
            //对接后的字符串进行sha1加密
            byte[] digest = md.digest(sb.toString().getBytes());
            checktext = byteToStr(digest);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return checktext != null && checktext.equals(signature.toUpperCase());
    }

    @Override
    public void getCallBackParam(HttpServletRequest request) {
        String openId = null;
        String eventKey = null;
        try {
            Map<String, String> messageMap = MessageUtil.parseXml(request);
            log.info("公众号回调解析XML文件结果{}", messageMap);
            //openId
            openId = messageMap.get("FromUserName");
            //二维码生成参数
            eventKey = messageMap.get("EventKey");
            eventKey = eventKey.substring(eventKey.lastIndexOf('|') + 1, eventKey.length());
        } catch (Exception e) {
            e.printStackTrace();
        }
        //处理数据
        SysUser sysUser = sysUserService.selectUserByUserName(eventKey);
        log.info("查询的用户信息{}", sysUser);
        if (sysUser != null && StringUtils.isEmpty(sysUser.getOpenId())) {
            //进行修改
            sysUser.setOpenId(openId);
            sysUserService.updateUserProfile(sysUser);
        }
    }

    @Override
    public String getOpenId(String userName) {
        String openId = null;
        SysUser sysUser = sysUserService.selectUserByUserName(userName);
        if (sysUser != null && !StringUtils.isEmpty(sysUser.getOpenId())) {
            openId = sysUser.getOpenId();
        }
        return openId;
    }

    /**
     * 将字节数组转化我16进制字符串
     *
     * @param byteArrays 字符数组
     * @return 字符串
     */
    private static String byteToStr(byte[] byteArrays) {
        String str = "";
        for (int i = 0; i < byteArrays.length; i++) {
            str += byteToHexStr(byteArrays[i]);
        }
        return str;
    }

    /**
     * 将字节转化为十六进制字符串
     *
     * @param myByte 字节
     * @return 字符串
     */
    private static String byteToHexStr(byte myByte) {
        char[] Digit = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
        char[] tampArr = new char[2];
        tampArr[0] = Digit[(myByte >>> 4) & 0X0F];
        tampArr[1] = Digit[myByte & 0X0F];
        String str = new String(tampArr);
        return str;
    }

    /**
     * 获取公众号账户信息
     *
     * @return
     */
    private OfficialAccount getAccount() {
        OfficialAccount one = officialAccountService.getOfficialAccount(new OfficialAccount());
        if (one == null) {
            //获取管理员账户
            one = officialAccountService.getCompanyOne(new OfficialAccount());
            if (one == null) {
                throw new CustomException("没有查询到对应的账户配置信息");
            }
        }
        return one;
    }
}
